package com.darren.framework.utils;

import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.alibaba.fastjson.JSONObject;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

public class JWTUtil {
	
	private static Logger logger = LogManager.getLogger(JWTUtil.class);
	
	/**
	 * Access Token 的密钥
	 */
	private static String ACCESS_SECRET =  "HONGHUJWT1234567890QWERTYUIOPASD";
	
	/**
	 * Refresh Token 的密钥
	 */
	private static String REFRESH_SECRET = "BAA2277AE3BDE436A40E2AC5F4AB72D5";	
	
	/**
	 * Access Token 的有效期时长，默认15分钟
	 */
	private static long ACCESS_MAXAGE = 900000L;
	
	/**
	 * Refresh Token 的有效期时长，默认半小时
	 */
	private static long REFRESH_MAXAGE = 1800000L;

	/**
	 * Access Token 的加密方式
	 */
    private static SignatureAlgorithm ACCESS_ALGORITHM;
    
    /**
     * Refresh Token 的加密方式
     */
    private static SignatureAlgorithm REFRESH_ALGORITHM;
    
    /**
     * jwt签发者
     */
    private static String ISSUER = "Archive";
    
    /**
     * 设置jwt签发者
     * @param issuer
     */
    public static void setIssuer(String issuer){
    	ISSUER = issuer;
    }
    
    /**
     * 设置Access 有效期时长
     * @param accessMaxAge
     */
    public static void setAccessExpired(long accessMaxAge){
    	ACCESS_MAXAGE = accessMaxAge;
    }
    
    /**
     * 获取Access 有效期时长
     * @return
     */
    public static long getAccessExpired(){
    	return ACCESS_MAXAGE;
    }
    
    /**
     * 设置Refresh 有效期时长
     * @param accessMaxAge
     */
    public static void setRefreshExpired(long refreshMaxAge){
    	REFRESH_MAXAGE = refreshMaxAge;
    }
    
    /**
     * 获取Refresh 有效期时长
     * @return
     */
    public static long getRefreshExpired(){
    	return REFRESH_MAXAGE;
    }
    
    /**
     * 设置Access Token 的密钥
     * @param accessSecret
     */
    public static void setAccessSecret(String accessSecret){
    	ACCESS_SECRET = accessSecret;
    }
    
    /**
     * 设置Refresh Token 的密钥
     * @param accessSecret
     */
    public static void setRefreshSecret(String refreshSecret){
    	REFRESH_SECRET = refreshSecret;
    }

    /**
     * 设置加密方式
     */
    static{
    	ACCESS_ALGORITHM = SignatureAlgorithm.HS512;
    	REFRESH_ALGORITHM = SignatureAlgorithm.HS384;    	
    }
    
    /**
     * 生成Access Token
     * @param payload
     * @return
     */
    public static <T> String signAccessJWT(T payload){    	
    	return signAccessJWT(payload,ACCESS_MAXAGE);
    }
    
    /**
     * 生成Access Token
     * @param payload
     * @param maxAge 0 表未永不过期
     * @return
     */
    public static <T> String signAccessJWT(T payload,long maxAge) {    	
    	long expTime = maxAge >= 0 ? maxAge: ACCESS_MAXAGE;
    	return signJWT(payload,ACCESS_ALGORITHM,ACCESS_SECRET,expTime);
    }
    
    /**
     * 生成Refresh Token
     * @param payload
     * @return
     */
    public static <T> String signRefreshJWT(T payload){    	
    	return signRefreshJWT(payload,ACCESS_MAXAGE);
    }
    
    /**
     * 生成Refresh Token
     * @param payload
     * @param maxAge 0 表未永不过期
     * @return
     */
    public static <T> String signRefreshJWT(T payload,long maxAge) {    	
    	long expTime = maxAge >= 0 ? maxAge: REFRESH_MAXAGE;
    	return signJWT(payload,REFRESH_ALGORITHM,REFRESH_SECRET,expTime);
    }
    
    /**
     * 生成Token
     * @param payload
     * @param signatureAlgorithm
     * @param secret
     * @param maxAge
     * @return
     */
	private static <T> String signJWT(T payload,SignatureAlgorithm signatureAlgorithm,String secret,long maxAge) {
		long nowMillis = System.currentTimeMillis();
		Date now = new Date(nowMillis);
		byte[] secretBytes = DatatypeConverter.parseBase64Binary(secret);
		SecretKey signingKey = new SecretKeySpec(secretBytes, signatureAlgorithm.getJcaName());
		String payloadString = JSONObject.toJSONString(payload);
		// 添加构成JWT的参数
		JwtBuilder builder =  Jwts.builder().setHeaderParam("typ", "JWT")
											.setIssuedAt(now) // iat: jwt的签发时间
											.setSubject(payloadString) // 主题 sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志,也差不多是个人的一些信息 
											.setIssuer(ISSUER) // jwt签发者
											.signWith(signatureAlgorithm, signingKey); // 估计是第三段密钥
		// 添加Token过期时间
		if (maxAge > 0) {
			// 过期时间
			long expMillis = nowMillis + maxAge;
			// 现在是什么时间
			Date exp = new Date(expMillis);
			// 系统时间之前的token都是不可以被承认的
			builder.setExpiration(exp)//exp: jwt的过期时间，这个过期时间必须要大于签发时间
					.setNotBefore(now);//nbf: 定义在什么时间之前，该jwt都是不可用的			
		}
		// 生成JWT字符串
		return builder.compact();
	}
	
	
	/**
	 * 解密Access jwt
	 * @param jwt
	 * @param clazz
	 * @return
	 */
	public static <T> JSONObject unsignAccessJWT(String jwt, Class<T> clazz){
		return unsignJWT(jwt,clazz,ACCESS_ALGORITHM,ACCESS_SECRET);
	}
	
	/**
	 * 解密Refresh jwt
	 * @param jwt
	 * @param clazz
	 * @return
	 */
	public static <T> JSONObject unsignRefreshJWT(String jwt, Class<T> clazz){
		return unsignJWT(jwt,clazz,REFRESH_ALGORITHM,REFRESH_SECRET);
	}
	
	/**
	 * 解密Access jwt
	 * @param jwt
	 * @return
	 */
	public static JSONObject unsignAccessJWTToJson(String jwt) {
		return unsignAccessJWT(jwt,JSONObject.class);
	}
	
	/**
	 * 解密Refresh jwt
	 * @param jwt
	 * @return
	 */
	public static JSONObject unsignRefreshJWTToJson(String jwt) {
		return unsignRefreshJWT(jwt,JSONObject.class);
	}
 
	
	/**
	 * 解密 jwt
	 * @param jwtString
	 * @param clazz
	 * @param signatureAlgorithm
	 * @param secret
	 * @return
	 */
	private static <T> JSONObject unsignJWT(String jwtString,Class<T> clazz,SignatureAlgorithm signatureAlgorithm,String secret){
		logger.info(jwtString);
		JSONObject resultJson = new JSONObject(); 
		byte[] encodedKey = DatatypeConverter.parseBase64Binary(secret);
		SecretKey key = new SecretKeySpec(encodedKey, signatureAlgorithm.getJcaName());// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
		try {
			Claims claims = Jwts.parser() // 得到DefaultJwtParser
					.setSigningKey(key) // 设置签名的秘钥
					.parseClaimsJws(jwtString).getBody();// 设置需要解析的jwt
			String payloadString = claims.getSubject();
			JSONObject payloadJson = JSONObject.parseObject(payloadString);
			T payload = JSONObject.toJavaObject(payloadJson, clazz);
			resultJson.put("payload", payload);
			resultJson.put("status", TokenStatus.AVILIBLE);
			resultJson.put("message", TokenStatus.AVILIBLE.getMessage());
		}catch (IllegalArgumentException illegalArgumentException) {
			resultJson.put("status", TokenStatus.FORMATERRO);
			resultJson.put("message", TokenStatus.FORMATERRO.getMessage());
			logger.error(illegalArgumentException.getMessage(), illegalArgumentException);
		}catch (MalformedJwtException malformedJwtException) {
			resultJson.put("status", TokenStatus.FORMATERRO);
			resultJson.put("message", TokenStatus.FORMATERRO.getMessage());
			logger.error(malformedJwtException.getMessage(), malformedJwtException);
		}catch (SignatureException signatureException) {
			resultJson.put("status", TokenStatus.INVALIDSIGNATURE);
			resultJson.put("message", TokenStatus.INVALIDSIGNATURE.getMessage());
			logger.error(signatureException.getMessage(), signatureException);
		} catch (ExpiredJwtException expiredJwtException) {
			resultJson.put("status", TokenStatus.EXPIRED);
			resultJson.put("message", TokenStatus.EXPIRED.getMessage());
			logger.error(expiredJwtException.getMessage(), expiredJwtException);
		}catch (Exception exception) {
			resultJson.put("status", TokenStatus.UNKNOWN);
			resultJson.put("message", TokenStatus.UNKNOWN.getMessage());
			logger.error(exception.getMessage(), exception);
		}
		return resultJson;
	}
    
    /**
     * JWT Token 的状态
     * @author Darren Zhou
     */
	public static enum TokenStatus {  
		AVILIBLE("有效的token。",0),
		TOKENNULLOREMPTY("token 不可以为空。",-1),
		FORMATERRO("token格式错误！",-2),
		INVALIDSIGNATURE("无效token签名。",-3),
		INVALIDTOKEN("无效的token。",-4),
		EXPIRED("token已过期。", -5), 
		UNKNOWN("未知错误。", -9999); 
		
	    // 成员变量  
	    private String message;  
	    private int code;
	    // 构造方法  
	    private TokenStatus(String message, int code) {  
	        this.message = message;  
	        this.code = code;  
	    }  
	    // 普通方法  
	    public static String getMessage(int code) {  
	        for (TokenStatus status : TokenStatus.values()) {  
	            if (status.getCode() == code) {  
	                return status.message;
	            }  
	        }  
	        return null;  
	    }  
	    // get set 方法  
	    public String getMessage() {  
	        return message;  
	    }  
	    public void setMessage(String message) {  
	        this.message = message;  
	    }  
	    public int getCode() {  
	        return code;  
	    }  
	    public void setCode(int code) {  
	        this.code = code;  
	    }  
	} 
}

